#!/usr/bin/env python
# Copyright 2016 Red Hat, Inc.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import argparse
import logging
import os
import pwd
import shutil
import sys
import time
import getpass
from ipapython.ipautil import run, user_input
from ipalib import api
from ipalib import errors
from novajoin.errors import ConfigurationError
from novajoin import configure_ipa
from six.moves.configparser import SafeConfigParser, NoOptionError
from urllib3.util import parse_url


IPACONF = '/etc/ipa/default.conf'
NOVACONF = '/etc/nova/nova.conf'
NOVACPUCONF = '/etc/nova/nova-cpu.conf'
NEUTRONCONF = '/etc/neutron/neutron.conf'
JOINCONF = '/etc/novajoin/join.conf'


LOGFILE = '/var/log/novajoin-install.log'
logger = logging.getLogger()


def openlogs():
    global logger  # pylint: disable=W0603
    if os.path.isfile(LOGFILE):
        try:
            created = '%s' % time.strftime(
                '%Y%m%d%H%M%SZ', time.gmtime(os.path.getctime(LOGFILE)))
            shutil.move(LOGFILE, '%s.%s' % (LOGFILE, created))
        except IOError:
            pass
    logger = logging.getLogger()
    try:
        lh = logging.FileHandler(LOGFILE)
    except IOError as e:
        print >> sys.stderr, 'Unable to open %s (%s)' % (LOGFILE, str(e))
        lh = logging.StreamHandler(sys.stderr)
    formatter = logging.Formatter('[%(asctime)s] %(message)s')
    lh.setFormatter(formatter)
    lh.setLevel(logging.DEBUG)
    logger.addHandler(lh)
    logger.propagate = False
    ch = logging.StreamHandler(sys.stdout)
    formatter = logging.Formatter('%(message)s')
    ch.setFormatter(formatter)
    ch.setLevel(logging.INFO)
    logger.addHandler(ch)


def install(opts):
    logger.info('Installation initiated')

    if not os.path.exists(opts.ipa_conf):
        raise ConfigurationError('Must be enrolled in IPA')

    try:
        os.environ['OS_PASSWORD']
        os.environ['OS_USERNAME']
        os.environ['OS_AUTH_URL']
    except KeyError as e:
        raise ConfigurationError('%s environment variable not set.' % e)

    try:
        pwd.getpwnam(opts.user)
    except KeyError:
        raise ConfigurationError('User: %s not found on the system' %
                                 opts.user)

    api.bootstrap(context='novajoin')

    novajoin = configure_ipa.NovajoinRole(user=opts.user)
    if not opts.no_kinit:
        novajoin.kinit(opts.principal, api.env.realm, opts.password)

    api.finalize()

    try:
        api.Backend.rpcclient.connect()
    except errors.CCacheError:
        raise ConfigurationError("No Kerberos credentials")

    logger.info('Installing default config files')

    keystone_url = parse_url(opts.keystone_auth_url)

    config = SafeConfigParser()
    config.read(opts.novajoin_conf)
    config.set('DEFAULT', 'domain', api.env.domain)  # pylint: disable=no-member
    config.set('DEFAULT',
               'api_paste_config',
               '/etc/novajoin/join-api-paste.ini')
    config.set('DEFAULT',
               'log_dir',
               '/var/log/novajoin')

    if not config.has_section('service_credentials'):
        config.add_section('service_credentials')

    config.set('service_credentials', 'auth_url', opts.keystone_auth_url)
    config.set('service_credentials', 'password', opts.nova_password)
    config.set('service_credentials', 'username', 'nova')
    config.set('service_credentials', 'auth_type', 'password')
    config.set('service_credentials', 'project_domain_name', 'default')
    config.set('service_credentials', 'project_name', opts.project_name)
    config.set('service_credentials', 'user_domain_id', 'default')

    if not config.has_section('keystone_authtoken'):
        config.add_section('keystone_authtoken')

    config.set('keystone_authtoken', 'memcached_servers',
               '%s:11211' % keystone_url.hostname)
    config.set('keystone_authtoken', 'project_name', opts.project_name)
    config.set('keystone_authtoken', 'password', opts.nova_password)
    config.set('keystone_authtoken', 'username', 'nova')
    config.set('keystone_authtoken', 'auth_url', opts.keystone_auth_url)
    config.set('keystone_authtoken', 'auth_type', 'password')
    config.set('keystone_authtoken', 'project_domain_name', 'default')
    config.set('keystone_authtoken', 'user_domain_id', 'default')

    if opts.notification_format == 'versioned':
        config.set('DEFAULT', 'notification_format', 'versioned')
    else:
        config.set('DEFAULT', 'notification_format', 'unversioned')

    config.set('DEFAULT', 'notifications_topic', 'novajoin_notifications')

    with open(opts.novajoin_conf, 'w') as f:
        config.write(f)

    config = SafeConfigParser()
    config.read(opts.nova_conf)
    config.set('DEFAULT',
               'vendordata_jsonfile_path',
               '/etc/novajoin/cloud-config-novajoin.json')

    # set the default domain to the IPA domain. This is added to the
    # instance name to set the hostname.
    config.set('DEFAULT',
               'dhcp_domain',
               api.env.domain)

    # Novajoin service
    config.set('DEFAULT',
               'vendordata_providers',
               'StaticJSON, DynamicJSON')

    config.set('DEFAULT',
               'vendordata_dynamic_targets',
               'join@http://127.0.0.1:9090/v1/')

    if not config.has_section('vendordata_dynamic_auth'):
        config.add_section('vendordata_dynamic_auth')

    config.set('vendordata_dynamic_auth', 'project_name', opts.project_name)
    config.set('vendordata_dynamic_auth', 'password', opts.nova_password)
    config.set('vendordata_dynamic_auth', 'username', 'nova')
    config.set('vendordata_dynamic_auth', 'auth_url', opts.keystone_auth_url)
    config.set('vendordata_dynamic_auth', 'auth_type', 'password')
    config.set('vendordata_dynamic_auth', 'project_domain_name', 'default')
    config.set('vendordata_dynamic_auth', 'user_domain_id', 'default')

    try:
        transport_url = config.get('DEFAULT', 'transport_url')
    except NoOptionError:
        transport_url = None

    with open(opts.nova_conf, 'w') as f:
        config.write(f)

    # Notifications
    for conf in set([opts.nova_conf, opts.nova_cpu_conf]):
        config = SafeConfigParser()
        config.read(conf)
        if not config.has_section('notifications'):
            config.add_section('notifications')

        config.set('notifications',
                   'notify_on_state_change',
                   'vm_state')

        if opts.notification_format == 'versioned':
            config.set('notifications',
                       'notification_format',
                       'versioned')
            config.set('notifications',
                       'versioned_notifications_topics',
                       'versioned_notifications,novajoin_notifications')
        else:
            config.set('notifications',
                       'notification_format',
                       'unversioned')
            config.set('oslo_messaging_notifications',
                       'topics',
                       'notifications,novajoin_notifications')

        with open(conf, 'w') as f:
            config.write(f)

    config = SafeConfigParser()
    config.read(opts.neutron_conf)
    config.set('oslo_messaging_notifications',
               'driver',
               'messagingv2')
    config.set('oslo_messaging_notifications',
               'topics',
               'notifications,novajoin_notifications')
    with open(opts.neutron_conf, 'w') as f:
        config.write(f)


    if transport_url:
        join_config = SafeConfigParser()
        join_config.read(opts.novajoin_conf)
        join_config.set('DEFAULT', 'transport_url', transport_url)
        with open(opts.novajoin_conf, 'w') as f:
            join_config.write(f)

    logger.info('Importing IPA metadata')
    #NOTE(xek): pass in os.environ, because ipautils overwrites PATH otherwise
    (stdout, stderr, returncode) = run(
        ['glance',
         '--os-image-api-version',
         '2',
         'md-namespace-import',
         '--file',
         '/usr/share/novajoin/freeipa.json'],
        raiseonerr=False,
        env=os.environ)
    if returncode != 0:
        logger.error('Adding IPA metadata failed: %s' % stderr)

    logger.info('Creating IPA permissions')

    novajoin.configure_ipa(precreate=False)


def parse_args():
    parser = argparse.ArgumentParser(description='Nova join Install Options')
    parser.add_argument('--keystone-auth-url', dest='keystone_auth_url',
                        help='Keystone auth URL', default=None)
    parser.add_argument('--nova-password', dest='nova_password',
                        help='Nova service user password', default=None)
    parser.add_argument('--project', dest='project_name',
                        help='Keystone project', default='service')
    parser.add_argument('--ipa-conf', dest='ipa_conf',
                        help='IPA configuration file', default=IPACONF)
    parser.add_argument('--nova-conf', dest='nova_conf',
                        help='nova configuration file', default=NOVACONF)
    parser.add_argument('--nova-cpu-conf', dest='nova_cpu_conf',
                        help='nova compute configuration file',
                        default=NOVACPUCONF)
    parser.add_argument('--neutron-conf', dest='neutron_conf',
                        help='neutron configuration file',
                        default=NEUTRONCONF)
    parser.add_argument('--novajoin-conf', dest='novajoin_conf',
                        help='novajoin configuration file',
                        default=JOINCONF)
    parser.add_argument('--notification-format', dest='notification_format',
                        help='The format of notifications to emit and read.',
                        choices=['versioned', 'unversioned'],
                        default='versioned')
    parser = configure_ipa.ipa_options(parser)

    opts = parser.parse_args()

    configure_ipa.validate_options(opts)

    if not opts.keystone_auth_url:
        opts.keystone_auth_url = user_input("Keystone auth URL", "",
                                            allow_empty=False)

    if not opts.nova_password:
        try:
            opts.nova_password = getpass.getpass("nova service Password: ")
        except EOFError:
            opts.nova_password = None
        if not opts.nova_password:
            raise ConfigurationError('nova service user password required.')

    try:
        pwd.getpwnam(opts.user)
    except KeyError:
        raise ConfigurationError('User: %s not found on the system' %
                                 opts.user)

    return opts

if __name__ == '__main__':
    opts = []
    out = 0
    openlogs()
    logger.setLevel(logging.DEBUG)

    try:
        opts = parse_args()

        logger.debug('Installation arguments:')
        logger.debug(opts)

        install(opts)
    except Exception as e:  # pylint: disable=broad-except
        logger.info(str(e))          # emit message to console
        logger.debug(e, exc_info=1)  # add backtrace information to logfile

        logger.info('Installation aborted.')
        logger.info('See log file %s for details' % LOGFILE)
        out = 1
    except SystemExit:
        out = 1
        raise
    finally:
        if out == 0:
            logger.info('Installation complete.')
            logger.info(
                'Please restart nova-api to enable the join service.')
    sys.exit(out)
